/****************************************************************************
 * Copyright (c) 2004 Composent, Inc. and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors: Composent, Inc. - initial API and implementation
 *
 * SPDX-License-Identifier: EPL-2.0
 *****************************************************************************/

package org.eclipse.ecf.discovery;

import java.io.Serializable;
import java.net.URI;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IAdapterManager;
import org.eclipse.ecf.core.identity.Namespace;
import org.eclipse.ecf.discovery.identity.*;
import org.eclipse.ecf.internal.discovery.DiscoveryPlugin;

/**
 * Base implementation of {@link IServiceInfo}. Subclasses may be created as
 * appropriate.
 */
public class ServiceInfo implements IServiceInfo, Serializable {

	private static final long serialVersionUID = -5651115550295457142L;

	/**
	 * @since 4.0
	 */
	public static final long DEFAULT_TTL = 3600; // 1h
	public static final int DEFAULT_PRIORITY = 0;
	public static final int DEFAULT_WEIGHT = 0;
	public static final String UNKNOWN_PROTOCOL = "unknown"; //$NON-NLS-1$

	/**
	 * @since 3.0
	 */
	protected String serviceName;

	protected IServiceID serviceID;

	protected int priority;

	protected int weight;

	protected IServiceProperties properties;

	/**
	 * @since 4.0
	 */
	protected long timeToLive;

	protected ServiceInfo() {
		// null constructor for subclasses
	}

	/**
	 * Create an IServiceInfo instance.
	 * 
	 * @param anURI
	 *            The (absolute) location of the service.
	 * @param aServiceName
	 *            a user chosen service name. Only ASCII characters are allowed.
	 * @param aServiceTypeID
	 *            the service type identifier.
	 * @since 3.0
	 */
	public ServiceInfo(URI anURI, String aServiceName, IServiceTypeID aServiceTypeID) {
		this(anURI, aServiceName, aServiceTypeID, DEFAULT_PRIORITY, DEFAULT_WEIGHT, null);
	}

	/**
	 * Create an IServiceInfo instance.
	 * 
	 * @param anURI
	 *            The (absolute) location of the service.
	 * @param aServiceName
	 *            a user chosen service name. Only ASCII characters are allowed.
	 * @param aServiceTypeID
	 *            the service type identifier.
	 * @param props
	 *            generic service properties.
	 * @since 3.0
	 */
	public ServiceInfo(URI anURI, String aServiceName, IServiceTypeID aServiceTypeID, IServiceProperties props) {
		this(anURI, aServiceName, aServiceTypeID, DEFAULT_PRIORITY, DEFAULT_WEIGHT, props);
	}

	/**
	 * Create an IServiceInfo instance.
	 * 
	 * @param anURI
	 *            The (absolute) location of the service.
	 * @param aServiceName
	 *            a user chosen service name. Only ASCII characters are allowed.
	 * @param aServiceTypeID
	 *            the service type identifier.
	 * @param priority
	 *            the service priority. The priority of this target host. A
	 *            client MUST attempt to contact the target host with the
	 *            lowest-numbered priority it can reach; target hosts with the
	 *            same priority SHOULD be tried in an order defined by the
	 *            weight field.
	 *
	 * @param weight
	 *            the service weight. A server selection mechanism. The weight
	 *            field specifies a relative weight for entries with the same
	 *            priority. Larger weights SHOULD be given a proportionately
	 *            higher probability of being selected. Domain administrators
	 *            SHOULD use Weight 0 when there isn't any server selection to
	 *            do. In the presence of records containing weights greater than
	 *            0, records with weight 0 should have a very small chance of
	 *            being selected.
	 * @param props
	 *            generic service properties.
	 * @since 3.0
	 */
	public ServiceInfo(URI anURI, String aServiceName, IServiceTypeID aServiceTypeID, int priority, int weight,
			IServiceProperties props) {
		this(anURI, aServiceName, aServiceTypeID, priority, weight, props, DEFAULT_TTL);
	}

	/**
	 * Create an IServiceInfo instance.
	 * 
	 * @param anURI
	 *            The (absolute) location of the service.
	 * @param aServiceName
	 *            a user chosen service name. Only ASCII characters are allowed.
	 * @param aServiceTypeID
	 *            the service type identifier.
	 * @param priority
	 *            the service priority. The priority of this target host. A
	 *            client MUST attempt to contact the target host with the
	 *            lowest-numbered priority it can reach; target hosts with the
	 *            same priority SHOULD be tried in an order defined by the
	 *            weight field.
	 *
	 * @param weight
	 *            the service weight. A server selection mechanism. The weight
	 *            field specifies a relative weight for entries with the same
	 *            priority. Larger weights SHOULD be given a proportionately
	 *            higher probability of being selected. Domain administrators
	 *            SHOULD use Weight 0 when there isn't any server selection to
	 *            do. In the presence of records containing weights greater than
	 *            0, records with weight 0 should have a very small chance of
	 *            being selected.
	 * @param props
	 *            generic service properties.
	 * @param ttl
	 *            time to live
	 * 
	 * @since 4.0
	 */
	public ServiceInfo(URI anURI, String aServiceName, IServiceTypeID aServiceTypeID, int priority, int weight,
			IServiceProperties props, long ttl) {
		Assert.isNotNull(anURI);
		Assert.isNotNull(aServiceName);
		Assert.isNotNull(aServiceTypeID);
		// Assert.isLegal(anURI.isOpaque(), "Opaque URI is not supported");
		// Assert.isLegal(!anURI.isAbsolute(),
		// "Non absolute URI is not supported");

		// [scheme:][//authority][path][?query][#fragment]

		// Scheme -> Protocol
		String scheme = anURI.getScheme();
		if (scheme == null) {
			scheme = UNKNOWN_PROTOCOL;
		}

		// UserInfo
		String userInfo = anURI.getUserInfo();
		if (userInfo == null) {
			userInfo = "";
		} else {
			userInfo += "@";
		}

		// Host
		String host = anURI.getHost();
		Assert.isNotNull(host);

		// Port
		int port = anURI.getPort();
		if (port == -1) {
			port = 0;
		}

		// Path
		String path = anURI.getPath();
		if (path == null) {
			path = "/";
		}

		// query
		String query = anURI.getQuery();
		if (query == null) {
			query = "";
		} else {
			query = "?" + query;
		}

		// fragment
		String fragment = anURI.getFragment();
		if (fragment == null) {
			fragment = "";
		} else {
			fragment = "#" + fragment;
		}
		URI uri = URI.create(scheme + "://" + userInfo + host + ":" + port + path + query + fragment);

		// service id
		Namespace ns = aServiceTypeID.getNamespace();
		this.serviceID = (IServiceID) ns.createInstance(new Object[] { aServiceTypeID, uri });
		((ServiceID) serviceID).setServiceInfo(this);

		this.serviceName = aServiceName;

		this.weight = weight;
		this.priority = priority;

		properties = (props == null) ? new ServiceProperties() : props;

		this.timeToLive = ttl;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.discovery.IServiceInfo#getAddress()
	 */
	public URI getLocation() {
		return serviceID.getLocation();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.discovery.IServiceInfo#getServiceID()
	 */
	public IServiceID getServiceID() {
		return serviceID;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.discovery.IServiceInfo#getPriority()
	 */
	public int getPriority() {
		return priority;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.discovery.IServiceInfo#getWeight()
	 */
	public int getWeight() {
		return weight;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.discovery.IServiceInfo#getServiceProperties()
	 */
	public IServiceProperties getServiceProperties() {
		return properties;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		final StringBuffer buf = new StringBuffer("ServiceInfo["); //$NON-NLS-1$
		buf.append("uri=").append(getLocation()).append(";id=").append(serviceID) //$NON-NLS-1$ //$NON-NLS-2$
				.append(";priority=") //$NON-NLS-1$
				.append(priority).append(";weight=").append(weight) //$NON-NLS-1$
				.append(
						";props=")
				.append(properties).append("]"); //$NON-NLS-1$
		return buf.toString();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
	 */
	public Object getAdapter(Class adapter) {
		if (adapter.isInstance(this)) {
			return this;
		}
		final IAdapterManager adapterManager = DiscoveryPlugin.getDefault().getAdapterManager();
		if (adapterManager == null)
			return null;
		return adapterManager.loadAdapter(this, adapter.getName());
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.discovery.IServiceInfo#getServiceName()
	 * @since 3.0
	 */
	public String getServiceName() {
		return serviceName;
	}

	/**
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ecf.discovery.IServiceInfo#getTTL()
	 * @since 4.0
	 */
	public long getTTL() {
		return timeToLive;
	}
}
